package com.gome.ocean.controller.gddl.app;

import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.gome.ocean.common.constants.Constants;
import com.gome.ocean.common.enums.DataBaseType;
import com.gome.ocean.common.enums.ShardAlgorithm;
import com.gome.ocean.common.enums.ShardOrder;
import com.gome.ocean.common.exception.GddlShardAppVOCreateException;
import com.gome.ocean.common.utils.DBUtil;
import com.gome.ocean.common.utils.ResponsePage;
import com.gome.ocean.dao.model.datasource.DatasourceBO;
import com.gome.ocean.dao.model.diamond.ConfigInfoBO;
import com.gome.ocean.dao.model.gddl.AppBO;
import com.gome.ocean.dao.model.gddl.AppConfigBO;
import com.gome.ocean.dao.model.gddl.AppTableBO;
import com.gome.ocean.dao.model.gddl.GddlShardAppVO;
import com.gome.ocean.gddl.matrix.GddlAppBean;
import com.gome.ocean.gddl.matrix.GddlAppRuleConfig;
import com.gome.ocean.service.base.AuthorityBaseController;
import com.gome.ocean.service.datax.DatasourceService;
import com.gome.ocean.service.diamond.ConfigInfoService;
import com.gome.ocean.service.gddl.AppConfigSerivce;
import com.gome.ocean.service.gddl.AppService;
import com.gome.ocean.service.gddl.AppTableService;
import com.gome.ocean.service.gddl.GddlShardAppService;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.taobao.tddl.rule.TableRule;
import com.taobao.tddl.rule.VirtualTableRoot;
import com.taobao.tddl.rule.utils.StringXmlApplicationContext;

/**
 * Created by liuqingxia on 2016/3/22.
 */
@Controller
@RequestMapping("/tddl/app")
public class AppController extends AuthorityBaseController {

    private static final Logger LOG = LoggerFactory.getLogger(AppController.class);
    @Autowired
    private AppService          appService;
    @Autowired
    private AppTableService     appTableService;

    @Autowired
    private GddlShardAppService gddlShardAppService;

    @Autowired
    private DatasourceService datasourceService;

    @Autowired
    protected ConfigInfoService configInfoService;

    @Autowired
    private GddlAppBean      gddlAppBean;
    @Autowired
    private AppConfigSerivce appConfigSerivce;

    private static final MessageFormat DBGROUPS_FORMAT = new MessageFormat("com.taobao.tddl.v1_{0}_dbgroups");

    @RequestMapping(value = "/insert", method = RequestMethod.GET)
    public String toAppInsertPage(ModelMap m) {
        List<String> projectNames = null;
        try {
            if (isAdmin()) {
                projectNames = getAllProjectNames();
            } else {
                projectNames = getCurrentUserProjects();
            }
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            m.put("errorMsg", e.getMessage());
        }
        m.put("projectNames", projectNames);
        return "gddl/app/insert";
    }

    @RequestMapping(value = "/createGddlShardApp", method = RequestMethod.POST)
    @ResponseBody
    public ResponsePage createGddlShardApp() {

        ResponsePage page = new ResponsePage();

        /* 第一步：将参数组装成GddlShardAppVO对象 */
        GddlShardAppVO gddlShardAppVO;
        try {
            gddlShardAppVO = getGddlShardAppVO();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return page.setErrorMsg("参数异常，请检查参数后重新提交");
        }

        /* 第二步：校验GddlShardAppVO对象 */
        if (CollectionUtils.isEmpty(gddlShardAppVO.getShardDbs())) {
            return page.setErrorMsg("分表参数有误！请检查!");
        }
        if (StringUtils.isEmpty(gddlShardAppVO.getAppName())) {
            return page.setErrorMsg("应用名不能为空！请检查!");
        }
        if (checkAppExisted(gddlShardAppVO.getAppName())) {
            return page.setErrorMsg("应用名已经存在请检查！");
        }

        /* 第三步：判断各个数据源是否有插入权限 */
        if (gddlShardAppVO.isCreateTable()) {/* 需要建表的时候再判断 */
            for (Long datasourceId : gddlShardAppVO.getShardDbs()) {
                DatasourceBO datasourceBO = datasourceService.findDatasourceBOById(datasourceId);
                if (datasourceBO == null) {
                    return page.setErrorMsg("数据源不存在！datasourceId = " + datasourceId);
                }
                boolean hasInsert = DBUtil.hasInsertPrivilege(
                    DataBaseType.getDataBaseType(datasourceBO.getSourceType()),
                    datasourceBO.getJdbcUrl(),
                    datasourceBO.getUserName(),
                    datasourceBO.getPassword(),
                    Collections.<String> emptyList());
                if (!hasInsert) {
                    return page.setErrorMsg("数据源没有插入权限！jdbc_url = " + datasourceBO.getJdbcUrl());
                }
            }
        }

        /* 第四步：生成diamond及AppBO信息 */
        try {

            if (checkConfigExisted(gddlShardAppVO)) {/* 先检查配置信息是否已经存在 */
                return page.setErrorMsg("该分表配置已经存在");
            }

            boolean result = gddlShardAppService.createGddlShardApp(gddlShardAppVO);// diamond信息

            if (result) {
                AppBO appBO = appService.findAppBOByAppName(gddlShardAppVO.getAppName());
                if (appBO == null) {
                    appBO = new AppBO();
                    appBO.setAppName(gddlShardAppVO.getAppName());
                    appBO.setInit(0);
                    appBO.setProject(gddlShardAppVO.getProjectName());// 设置项目组信息，以备权限查询
                    appService.insertAppBO(appBO);
                }
                return page.setSuccessMsg("分库分表创建成功!");
            } else {
                return page.setErrorMsg("分库分表创建失败!");
            }

        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return page.setErrorMsg("新建tddl应用时发生异常，请联系管理员");
        }

    }

    /**
     * 检查diamond中是否已经存在同名的tddl分表配置信息
     *
     * @param gddlShardAppVO
     * @return
     */
    private boolean checkConfigExisted(GddlShardAppVO gddlShardAppVO) {
        ConfigInfoBO configInfoBO = configInfoService
            .findConfigInfoBOByDataId(DBGROUPS_FORMAT.format(new Object[] { gddlShardAppVO.getLogicTbName() }));

        return configInfoBO != null;
    }

    // 检查app是否已经存在
    private boolean checkAppExisted(String appName) {
        try {
            AppBO appBO = appService.findAppBOByAppName(appName);
            if (appBO == null) {
                return Boolean.FALSE;
            } else {
                return Boolean.TRUE;
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            LOG.error(e.getMessage());
            return Boolean.TRUE;
        }
    }

    @RequestMapping(value = "/checkGddlconfig", method = RequestMethod.POST)
    @ResponseBody
    public ResponsePage checkGddlconfig() {
        ResponsePage page = new ResponsePage();
        GddlShardAppVO gddlShardAppVO;
        try {
            gddlShardAppVO = getGddlShardAppVO();
        } catch (GddlShardAppVOCreateException e) {
            LOG.error(e.getMessage(), e);
            return page.setErrorMsg("参数异常，请检查参数后重新提交");
        }
        boolean result = gddlShardAppService.checkGddlShardApp(gddlShardAppVO);
        if (result) {
            return page.setSuccessMsg("分库分表创建成功!");
        } else {
            return page.setErrorMsg("分库分表创建失败!");
        }
    }

    /**
     * 根据前端传来的参数， 创建GddlShardAppVO
     * 
     * @return
     * @throws GddlShardAppVOCreateException
     */
    /**
     * 根据前端传来的参数， 创建GddlShardAppVO
     * 
     * @return
     * @throws GddlShardAppVOCreateException
     */
    private GddlShardAppVO getGddlShardAppVO() throws GddlShardAppVOCreateException {

        HttpServletRequest req = getHttpRequest();
        GddlShardAppVO gddlShardAppVO = new GddlShardAppVO();

        ConvertUtilsBean convertUtils = new ConvertUtilsBean();
        BeanUtilsBean beanUtils = new BeanUtilsBean(convertUtils, new PropertyUtilsBean());
        Enumeration names = req.getParameterNames();

        String gddlAttrMapStr = req.getParameter("appJsonArrayList");
        List<GddlShardAppVO> gddlAttrMap = new ArrayList<GddlShardAppVO>();
        JSONArray gddlAttrArray = JSONArray.parseArray(gddlAttrMapStr);
        for (int i = 0; i < gddlAttrArray.size(); i++) {
            JSONObject object = gddlAttrArray.getJSONObject(i);
            GddlShardAppVO tempGddlShardAppVO = new GddlShardAppVO();
            tempGddlShardAppVO.setAppName(req.getParameter("appName"));
            tempGddlShardAppVO.setShardAlgorithm(ShardAlgorithm.shardAlgorithm(object.getIntValue("shardAlgorithm")));
            tempGddlShardAppVO.setShardDbCount(object.getIntValue("shardDbCount"));
            tempGddlShardAppVO.setShardOrder(ShardOrder.getShardOrder(object.getIntValue("shardOrder")));
            tempGddlShardAppVO.setShardTbFild(object.getString("shardTbFild"));
            tempGddlShardAppVO.setShardTbCount(object.getInteger("shardTbCount"));
            tempGddlShardAppVO.setShardDbTbSameName(object.getBooleanValue("shardDbTbSameName"));
            tempGddlShardAppVO.setSuffixLength(object.getInteger("suffixLength"));
            tempGddlShardAppVO.setPhysicsTbPrefix(object.getString("physicsTbPrefix"));
            tempGddlShardAppVO.setLogicTbName(object.getString("logicTbName"));
            String[] shardDbs = req.getParameter("shardDbs").split(Constants.REG_CN_OR_EN_COMMA);
            tempGddlShardAppVO.setBack("1".equals(req.getParameter("isBack")) ? true : false);
            tempGddlShardAppVO
                .setShardNum(Math.abs(object.getInteger("shardTbCount") / object.getIntValue("shardDbCount")));

            List<Long> shardDbList = Lists.newArrayList();
            for (String shardDb : shardDbs) {
                shardDbList.add(Long.parseLong(shardDb));
            }
            tempGddlShardAppVO.setShardDbs(shardDbList);

            String[] backDbs = req.getParameter("backDbs").split(Constants.REG_CN_OR_EN_COMMA);
            if (backDbs.length > 0 && !("".equals(backDbs[0]))) {
                List<Long> backDbList = Lists.newArrayList();
                for (String backDb : backDbs) {
                    backDbList.add(Long.parseLong(backDb));
                }
                tempGddlShardAppVO.setBackDbs(backDbList);
            }
            gddlAttrMap.add(tempGddlShardAppVO);
        }
        gddlShardAppVO.setGddlAttrMap(gddlAttrMap);
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            String value = req.getParameter(name);
            if (StringUtils.isBlank(value)) {
                continue;
            }
            try {
                if (name.equals("shardDbTbSameName")) {
                    boolean isSame = Integer.parseInt(value) == 1 ? true : false;
                    gddlShardAppVO.setShardDbTbSameName(isSame);
                    continue;
                }
                if (name.equals("shardAlgorithm")) {
                    int algorithm = Integer.parseInt(value);
                    gddlShardAppVO.setShardAlgorithm(ShardAlgorithm.shardAlgorithm(algorithm));
                    continue;
                }
                if (name.equals("shardOrder")) {
                    int order = Integer.parseInt(value);
                    gddlShardAppVO.setShardOrder(ShardOrder.getShardOrder(order));
                    continue;
                }
                if (name.equals("shardDbs")) {
                    String[] shardDbs = value.split(Constants.REG_CN_OR_EN_COMMA);
                    List<Long> shardDbList = Lists.newArrayList();
                    for (String shardDb : shardDbs) {
                        shardDbList.add(Long.parseLong(shardDb));
                    }
                    gddlShardAppVO.setShardDbs(shardDbList);
                    continue;
                }
                if (name.equals("backDbs")) {
                    String[] backDbs = value.split(Constants.REG_CN_OR_EN_COMMA);
                    List<Long> shardDbList = Lists.newArrayList();
                    if (backDbs.length == 1 && !("".equals(backDbs[0]))) {
                        for (String backDb : backDbs) {
                            shardDbList.add(Long.parseLong(backDb));
                        }
                        gddlShardAppVO.setBackDbs(shardDbList);
                    }
                    continue;
                }
                if ("suffixLength"
                    .equals(name)) {/* 该属性的get方法特殊，beanUtils无法赋值 */
                    gddlShardAppVO.setSuffixLength(Integer.parseInt(value));
                    continue;
                }
                if ("isCreateTable".equals(name)) {
                    int isCreateTableInt = Integer.parseInt(value);
                    if (0 == isCreateTableInt) {
                        gddlShardAppVO.setCreateTable(false);
                        continue;
                    } else if (1 == isCreateTableInt) {
                        gddlShardAppVO.setCreateTable(true);
                        continue;
                    } else {
                        throw new GddlShardAppVOCreateException("isCreateTable参数不正常");
                    }
                }
                if ("isBack".equals(name)) {
                    int isBackInt = Integer.parseInt(value);
                    if (0 == isBackInt) {
                        gddlShardAppVO.setBack(false);
                        continue;
                    } else if (1 == isBackInt) {
                        gddlShardAppVO.setBack(true);
                        continue;
                    } else {
                        throw new GddlShardAppVOCreateException("isBack参数不正常");
                    }
                }
                beanUtils.setProperty(gddlShardAppVO, name, value);
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new GddlShardAppVOCreateException(e.getMessage());
            }
        }

        return gddlShardAppVO;
    }

    @RequestMapping(value = "/find", method = RequestMethod.GET)
    public String findGet() {
        return "gddl/app/find";
    }

    @RequestMapping(value = "/find", method = RequestMethod.POST)
    @ResponseBody
    public ResponsePage findPost() {
        ResponsePage<AppBO> page = new ResponsePage();
        int pageNum = getPageNum();
        int pageSize = getPageSize();
        try {
            Page<AppBO> appBOs;
            if (isAdmin()) {
                appBOs = appService.findAllApps(pageNum, pageSize);
            } else {
                List<String> projectNames = getCurrentUserProjects();
                appBOs = appService.findAllAppsByProjectNames(pageNum, pageSize, projectNames);
            }
            if (appBOs == null || appBOs.isEmpty()) {
                return page.emptyPage(FIND_NO_RESULTS);
            }
            return page.setPage(appBOs, FIND_SUCCESS);
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return page.setErrorMsg(e.getMessage());
        }
    }

    @RequestMapping(value = "/findById", method = RequestMethod.POST)
    @ResponseBody
    public ResponsePage findById(@RequestParam("id") long id) {
        ResponsePage<AppBO> page = new ResponsePage();
        AppBO appBO = null;
        try {
            appBO = appService.findAppBOById(id);
            if (appBO == null) {
                return page.emptyPage(FIND_NO_RESULTS);
            }
            return page.setSingleObject(appBO, FIND_SUCCESS);
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return page.setErrorMsg(e.getMessage());
        }
    }

    @RequestMapping(value = "/findByAppName", method = RequestMethod.POST)
    @ResponseBody
    public ResponsePage findByLogicTableName(@RequestParam("appName") String appName) {
        ResponsePage page = new ResponsePage();
        try {
            List<AppBO> list = appService.findAppBOsByAppName(appName);
            if (list == null) {
                return page.emptyPage(FIND_NO_RESULTS);
            }
            return page.setJustList(list, "查询成功");
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return page.setErrorMsg(e.getMessage());
        }
    }

    @RequestMapping("/saveAppConfig")
    @ResponseBody
    public String saveAppConfig(String appId, String isSwitch, String delayTime) {
        Long id = Long.parseLong(appId);
        Integer autoSwitch = Integer.parseInt(isSwitch);
        Integer delay_time = 0;
        if (StringUtils.isNotEmpty(delayTime)) {
            delay_time = Integer.parseInt(delayTime);
        }
        AppConfigBO appConfig = appConfigSerivce.findAppConfigBOByAppId(id);
        AppConfigBO configBO = new AppConfigBO();
        boolean flag = false;
        try {
            if (appConfig == null) {
                configBO.setAppId(id);
                configBO.setIsAutoSwitch(autoSwitch);
                configBO.setSwitchDelayTime(delay_time);
                flag = appConfigSerivce.saveAppConfigBO(configBO);
                if (flag) {
                    return "success";
                }
                return "error";
            } else {
                configBO.setId(appConfig.getId());
                configBO.setAppId(id);
                configBO.setIsAutoSwitch(autoSwitch);
                configBO.setSwitchDelayTime(delay_time);
                flag = appConfigSerivce.updateAppConfigBOById(configBO);
                if (flag) {
                    return "success";
                }
                return "error";
            }
        } catch (Exception e) {
            LOG.error(e.toString());
        }
        return "";
    }

    @RequestMapping("/reloadGddlShardApp")
    @ResponseBody
    public String reloadGddlShardApp(String appId, String appName) {
        try {
            ConfigInfoBO configInfoBO = configInfoService
                .findConfigInfoBOByDataId(DBGROUPS_FORMAT.format(new Object[] { appName }));

            gddlAppBean.processApp(configInfoBO);// 根据diamond配置信息，生成AppBO,并插入数据库

            AppBO appBO = appService.findAppBOByAppName(appName);
            if (null != appBO) {
                appBO.setInit(1);
                appService.updateAppBO(appBO);
            }
            return "success";
        } catch (Exception e) {
            LOG.error(e.toString());
        }
        return "error";
    }

    @RequestMapping("/findAllAppTableByAppId")
    @ResponseBody
    public ResponsePage findAllAppTableByAppId(@RequestParam("appId") long appId) {
        ResponsePage page = new ResponsePage();
        try {
            List<AppTableBO> appTableLists = appTableService.findAppTableByAppId(appId, getPageNum(), getPageSize());
            if (appTableLists == null && appTableLists.size() > 0) {
                return page.emptyPage(FIND_NO_RESULTS);
            }
            return page.setJustList(appTableLists, FIND_SUCCESS);
        } catch (Exception e) {
            LOG.error(e.toString());
            return page.setErrorMsg(e.toString());
        }
    }

    @RequestMapping("/validateRule")
    @ResponseBody
    public Map validateRule(@RequestParam("ruleConfig") String ruleConfig) {
        if (StringUtils.isEmpty(ruleConfig)) {
            return Maps.newHashMap();
        }
        Map<String, Map<String, Set<String>>> ruleMaps = new HashMap<String, Map<String, Set<String>>>();
        VirtualTableRoot virtualTableRoot = buildRuleByStr(ruleConfig);
        Map<String, TableRule> tableRules = virtualTableRoot.getTableRules();
        for (Entry<String, TableRule> tableRuleEntry : tableRules.entrySet()) {
            ruleMaps.put(tableRuleEntry.getKey(), tableRuleEntry.getValue().getActualTopology());
        }
        return ruleMaps;
    }

    private VirtualTableRoot buildRuleByStr(String data) {
        @SuppressWarnings("resource")
        ApplicationContext applicationContext = new StringXmlApplicationContext(data,
            GddlAppRuleConfig.outerClassLoader);
        VirtualTableRoot tempvtr = (VirtualTableRoot) applicationContext.getBean(GddlAppRuleConfig.ROOT_BEAN_NAME);
        return tempvtr;
    }

    @RequestMapping("/findAppConfigById")
    @ResponseBody
    public AppConfigBO findAppConfigById(String appId) {
        try {
            AppConfigBO appConfig = appConfigSerivce.findAppConfigBOByAppId(Long.parseLong(appId));
            if (appConfig != null) {
                return appConfig;
            }
        } catch (NumberFormatException e) {
            LOG.error(e.toString());
            return null;
        }
        return null;
    }

}
